文章目录
  1. 1. 类声明
  2. 2. 不存在变量提升(hoist)
  3. 3. 类表达式
  4. 4. 类体和方法定义
  5. 5. 严格模式
  6. 6. 构造器(constructor方法)
  7. 7. 原型方法
  8. 8. 静态方法
  9. 9. extends关键字
  10. 10. super 关键字
  11. 11. 类的Getter和Setter方法
  12. 12. 类的Generator方法

ES6中的类实际就是一个函数,且正如函数的定义方式有函数声明和函数表达式两种一样,类的定义也有两种方式.
分别为:

-类声明
-类表达式

类声明

类声明是定义类的一种方式,使用class关键字后跟一个类名,就可以定义一个类.

1
2
3
4
5
class Foo {
constructor{
//...
}
}

不存在变量提升(hoist)

类声明和函数声明不同的一点是,函数声明存在变量提升现象,而类声明不会.即,类必须先声明,然后才可以使用,否则会抛出ReferencError异常

1
2
3
4
var foo = new Foo(); // Uncaught ReferenceError: Foo is not defined(...)
class Foo {
// ...
}

这种规定的原因与类的继承有关,必须保证子类在父类之后定义。

1
2
3
4
let Foo = class {};

class Bar extends Foo {
}

上面的代码不会报错,因为class Bar继承Foo时,Foo已经有定义了。(但是,如果存在Class提升,上面代码就会报错,因为Class Bar会被提升到代码头部,而表达式式Foo是不会提升的,所以导致Class Bar继承Foo的时候,Foo还没有定义。)

类表达式

类表达式就定义类的另外一种方式,就像函数表达式一样,在类表达式中,类名是可有可无的。若定义的类名,则该类名只有的类的内部才可以访问到。

1
2
3
4
5
6
7
8
9
// 方式一
const MyClass = class {};

// 方式二:给出类名
const MyClass = class Me {
getClassName() {
return Me.name;
}
};

上面方式二定义类的同时给出了类名,此时,Me类名只可以在Class的内部代码可用,指代当前类。MyClass的name属性值为给出的类名。

1
2
3
4
let my = new MyClass();
my.getClassName(); // Me
Me.name; // Uncaught ReferenceError: Me is not defined(…)
MyClass.name; // Me

采用类表达式,可以写出立即执行的Class。如下:

1
2
3
4
5
6
7
8
9
10
11
let person = new class {
constructor(name) {
this.name = name;
}

sayName() {
console.log(this.name);
}
}('Zhang San');

person.sayName(); // Zhang San

类体和方法定义

类的成员需要定义在一对大括号内{},大括号内的代码的大括号本身组成了类体。类成员包括类构造器和类方法(包括静态方法和实例方法)。

严格模式

类体中的代码都强制在严格模式中执行,即默认”use strict”。考虑到未来所有的代码,其实都是运行在模块之中,所以ES6实际上把整个语言升级到了严格模式。

构造器(constructor方法)

constructor方法是一个特殊的类方法,它既不是静态方法也不是实例方法,它仅在实例化的时候被调用。一个类只能拥有一个名为constructor的方法,否则会抛出SyntaxError异常。

如果没有定义constructor方法,这个方法会被默认添加,即,不管有没有显示定义,任何一个类都有constructor方法。

子类必须在constructor方法中调用super方法,否则新建实例时会报错。因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象。

1
2
3
4
5
6
7
class Point {}

class ColorPoint extends Point {
constructor() {}
}

let cp = new ColorPoint(); // ReferenceError

上面代码中,ColorPoint继承了父类Point,但是它的构造函数没有调用super方法,导致新建实例时报错。

原型方法

定义类的方法时,方法名前面不需要加上function关键字。另外,方法之间不需要用逗号分隔,加了会报错。

1
2
3
4
5
6
7
8
9
class Bar {
constructor() {}

doStuff() {}

toString() {}

toValue() {}
}

类的所有方法都是定义在类的`prototype属性上的,上面的写法等同于下面:

1
2
3
4
5
Bar.prototype = {
doStuff() {},
toString() {},
toValue() {}
};

所以,在类的实例上调用方法,实际上就是调用原型上的方法。

1
2
3
4
class B {}
let b = new B();

b.constructor === B.prototype.constructor; // true

上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。
由于类的方法都是定义在prototype上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

1
2
3
4
5
6
7
8
9
10
class Point {
constructor() {
// ...
}
}

Object.assign(Point.prototype, {
toString() {},
toValue() {}
});

另外,类的内部所有定义的方法,都是不可枚举的(non-enumerable)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Point {
constructor(x, y) {
// ...
}

toString() {
return '(' + x + ', ' + y + ')';
}
}

Object.keys(Point.prototype); // []
Object.getOwnPropertyNames(Point.prototype); // ["constructor", "toString"]
Object.getOwnPropertyDescriptor(Point, 'toString');
// Object {writable: true, enumerable: false, configurable: true}

静态方法

static关键字用来定义类的静态方法。静态方法是指那些不需要对类进行实例化,使用类名就可以直接访问的方法。静态方法经常用来作为工具函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}

static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;

return Math.sqrt(dx*dx + dy*dy);
}
}

const p1 = new Point(5, 5);
const p2 = new Point(10, 10);

console.log(Point.distance(p1, p2));

静态方法不可以被实例继承,是通过类名直接调用的。但是,父类的静态方法可以被子类继承。

1
2
3
4
5
6
7
8
9
10
class Foo {
static classMethod() {
return 'hello';
}
}

class Bar extends Foo {
}

Bar.classMethod(); // "hello"

静态方法也可以用super关键字调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Foo {
static classMethod() {
return 'hello';
}
}

class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}

Bar.classMethod(); // "hello too"

extends关键字

extends关键字用于实现类之间的继承。子类继承父类,就继承了父类的所有属性和方法。
extends后面只可以跟一个父类。

super 关键字

super关键字可以用来调用其父类的构造器或方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Cat { 
constructor(name) {
this.name = name;
}

speak() {
console.log(this.name + ' makes a noise.');
}
}

class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}

类的Getter和Setter方法

与ES5一样,在类内部可以使用getset关键字,对某个属性设置取值和赋值方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {
constructor() {}

get prop() {
return 'getter';
}

set prop(val) {
console.log('setter: ' + val);
}
}

let foo = new Foo();

foo.prop = 1;
// setter: 1

foo.prop;
// "getter"

上面代码中,prop属性有对应 的赋值和取值方法,因此赋值和读取行为都被自定义了。
存值和取值方法是设置在属性的descriptor对象上的。

1
2
3
4
var descriptor = Object.getOwnPropertyDescriptor(Foo.prototype, 'prop');

"get" in descriptor // true
"set" in descriptor // true

上面代码中,存值和取值方法是定义在prop属性的描述对象上的,这与ES5一致。

类的Generator方法

如果类的某个方法名前加上星号(*),就表示这个方法是一个Generator函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}

for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// world

上面代码中,Foo类的Symbol.iterator方法前有一个星号,表示该方法是一个Generator函数。Symbol.iterator方法返回一个Foo类的默认遍历器,for...of循环会自动调用这个遍历器。

文章目录
  1. 1. 类声明
  2. 2. 不存在变量提升(hoist)
  3. 3. 类表达式
  4. 4. 类体和方法定义
  5. 5. 严格模式
  6. 6. 构造器(constructor方法)
  7. 7. 原型方法
  8. 8. 静态方法
  9. 9. extends关键字
  10. 10. super 关键字
  11. 11. 类的Getter和Setter方法
  12. 12. 类的Generator方法